package com.mos.iot.mqtt.listener;

import com.mos.iot.mqtt.constants.MqttConstants;
import com.mos.iot.mqtt.listener.annotation.*;
import com.mos.iot.mqtt.listener.converter.MessageConverter;
import com.mos.iot.mqtt.listener.converter.MessageConverterRegistry;
import com.mos.iot.mqtt.listener.converter.impl.BaseMessageConverter;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Getter
@Setter
public class MethodParam {

    private MessageConverterRegistry messageConverterRegistry = MessageConverterRegistry.INSTANCE;

    private MqttOutput mqttOutput;

    private MessageConverter outMessageConverter;

    private Map<String, ParamType> paramsType = new LinkedHashMap<>();

    private Map<String, MessageConverter> params = new LinkedHashMap<>();

    private Map<String, Class<?>> paramTarget = new LinkedHashMap<>();

    public MethodParam(Method method) {
        MqttOutput mqttOutput = method.getAnnotation(MqttOutput.class);
        if(mqttOutput != null) {
            this.mqttOutput = mqttOutput;
            this.outMessageConverter = messageConverterRegistry.getMessageConverter(mqttOutput.converter());
        }
        for (Parameter parameter : method.getParameters()) {
            MqttTopic mqttTopic = parameter.getAnnotation(MqttTopic.class);
            if(mqttTopic != null) {
                String name = parameter.getName();
                params.put(name, messageConverterRegistry.getMessageConverter(BaseMessageConverter.class));
                paramTarget.put(name, parameter.getType());
                paramsType.put(name, ParamType.Topic);
            }
            MqttTopicPath topicPath = parameter.getAnnotation(MqttTopicPath.class);
            if(topicPath != null) {
                String name = StrUtil.blankToDefault(topicPath.value(), parameter.getName());
                params.put(name, messageConverterRegistry.getMessageConverter(topicPath.converter()));
                paramTarget.put(name, parameter.getType());
                paramsType.put(name, ParamType.TopicPath);
            }
            MqttPayload mqttPayload = parameter.getAnnotation(MqttPayload.class);
            if(mqttPayload != null) {
                String name = parameter.getName();
                params.put(name, messageConverterRegistry.getMessageConverter(mqttPayload.converter()));
                paramTarget.put(name, parameter.getType());
                paramsType.put(name, ParamType.Payload);
            }
            TopicMessage topicMessage = parameter.getAnnotation(TopicMessage.class);
            if(topicMessage != null) {
                String name = parameter.getName();
                params.put(name, messageConverterRegistry.getMessageConverter(topicMessage.converter()));
                paramTarget.put(name, MqttTopicMessage.class);
                paramsType.put(name, ParamType.MqttMessage);
            }
        }
    }

    public Object[] getParams(String topic, Map<String, String> topicParams, MqttMessage message) {
        List<Object> list = new ArrayList<>();
        paramsType.forEach((name, paramType) -> {
            MessageConverter converter = params.get(name);
            Class<?> target = paramTarget.get(name);
            if(paramType == ParamType.Topic) {
                list.add(converter.convertWithCheck(topic, target, null, true));
            } else if(paramType == ParamType.TopicPath) {
                list.add(converter.convertWithCheck(topicParams.get(name), target, null, true));
            } else if(paramType == ParamType.Payload) {
                list.add(converter.convertWithCheck(message, target, null, true));
            } else if(paramType == ParamType.MqttMessage) {
                MqttTopicMessage mqttTopicMessage = converter.convertWithCheck(message, MqttTopicMessage.class, null, true);
                mqttTopicMessage.setTopic(topic);
                mqttTopicMessage.setTopicParam(topicParams);
                mqttTopicMessage.setQos(message.getQos());
                list.add(mqttTopicMessage);
            } else {
                list.add(null);
            }
        });
        return list.toArray();
    }

    public MqttTopicMessage getOutMessage(Object res, Map<String, String> topicParams) {
        return MqttTopicMessage.builder()
                .clientId(StrUtil.blankToDefault(mqttOutput.clientId(), MqttConstants.DEFAULT_CLIENT_ID))
                .topic(mqttOutput.topic())
                .topicParam(topicParams)
                .qos(mqttOutput.qos())
                .payload(res)
                .build();
    }

    public enum ParamType {
        Topic,
        TopicPath,
        Payload,
        MqttMessage,
        ;
    }

}
